/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package src.network;

import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import src.engine.PICAIUDice;
import java.util.concurrent.CopyOnWriteArrayList;
import src.engine.PICAIUVector;

/**
 * This class houses the server's side of the game. It will continually update and send data to the clients
 * @author James Daniello
 */
public class PICAIUGame extends Thread {

  //players
  private PICAIUServerPersona blueTeam;
  private PICAIUServerPersona redTeam;
  private PICAIUServerPersona startedGame;
  private boolean redTeamReady = false, blueTeamReady = false;
  private boolean redTeamPlaced = false, blueTeamPlaced = false;
  private boolean redTeamOK = false, blueTeamOK = false;
  //how much this battle is wortth
  private int minAmount = 0;
  private int maxAmount = 0;
  private String password = "";
  private boolean gameStarted = false;
  private PICAIUServer server;
  private boolean running = true;
  private int aiAct = 8;
  private int aiCount = aiAct;
  private CopyOnWriteArrayList<PICAIUBountyS> redBounties = new CopyOnWriteArrayList<PICAIUBountyS>();
  private CopyOnWriteArrayList<PICAIUBountyS> blueBounties = new CopyOnWriteArrayList<PICAIUBountyS>();
  ArrayList<PICAIURecruittedUnit> blueUnits = new ArrayList<PICAIURecruittedUnit>();
  ArrayList<PICAIURecruittedUnit> redUnits = new ArrayList<PICAIURecruittedUnit>();
  protected int mapWidth = 2000, mapHeight = 1500;
  protected ArrayList<PICAIUUndiscoveredLocation> undiscoveredLocationsBlue = new ArrayList<PICAIUUndiscoveredLocation>();
  protected ArrayList<PICAIUUndiscoveredLocation> undiscoveredLocationsRed = new ArrayList<PICAIUUndiscoveredLocation>();
  //blue starting points
  private Vector<PICAIUVector> blueBases = new Vector<PICAIUVector>();
  //red starting pointsgame
  private Vector<PICAIUVector> redBases = new Vector<PICAIUVector>();
  //all bases
  private Vector<PICAIUVector> bases = new Vector<PICAIUVector>();
  private int baseVision = 600;

  PICAIUGame(PICAIUServerPersona persona, String amount, String pass, PICAIUServer server) {
    int i = PICAIUDice.roll(2);
    if (i == 1) {
      redTeam = persona;
      persona.sendDataToClient("TEAM#RED");
    } else {
      blueTeam = persona;
      persona.sendDataToClient("TEAM#BLUE");
    }
    startedGame = persona;
    String[] data = amount.split("-");
    minAmount = Integer.parseInt(data[0]);
    maxAmount = Integer.parseInt(data[1]);
    password = pass;
    this.server = server;
    createBases();
    createUndiscoveredLocations();
  }

  public PICAIUServerPersona getRedPersona() {
    return redTeam;
  }

  public PICAIUServerPersona getBluePersona() {
    return blueTeam;
  }

  /**
   * returns the person who started the game
   * @return
   */
  public PICAIUServerPersona getStartedBy() {
    return startedGame;
  }

  /**
   * returns the amount for which this game is being played for, in the format which
   * shows up in the client (e.g '500-1000')
   */
  public String getFormattedGameAmount() {
    return "" + minAmount + "-" + maxAmount;
  }

  boolean hasGameStarted() {
    return gameStarted;
  }

  public String getFormattedGameName() {
    return startedGame.getUserName() + " >> " + "[$" + minAmount + "-" + maxAmount + "]";
  }

  protected String getPassword() {
    return password;
  }

  protected void playerJoins(PICAIUServerPersona persona) {
    if (startedGame == redTeam) {
      if (blueTeam != null) {
        return;
      }
      blueTeam = persona;
      persona.sendDataToClient("TEAM#BLUE");
    } else if (startedGame == blueTeam) {
      if (redTeam != null) {
        return;
      }
      redTeam = persona;
      persona.sendDataToClient("TEAM#RED");
    }
    persona.setGame(this);
    blueTeam.sendDataToClient("SETENEMYMONEY#" + redTeam.getUserName() + "#" + redTeam.getMoney() + "#" + redTeamReady);
    redTeam.sendDataToClient("SETENEMYMONEY#" + blueTeam.getUserName() + "#" + blueTeam.getMoney() + "#" + blueTeamReady);
    persona.sendDataToClient("GUI#pregame");
    server.sendDataToAll("RMVUSR#" + persona.getUserName());
    server.sendDataToAll("RMVGM#" + getFormattedGameName());

  }

  boolean hasTwoPlayers() {
    return redTeam != null && blueTeam != null;
  }

  void notifyOtherOfLeaving(PICAIUServerPersona persona) {
    if (running) {
      return;
    }
    if (persona == redTeam) {
      redTeam = null;
      blueTeamReady = redTeamReady = false;
      if (blueTeam == null) {
        server.removeGame(this);
        running = false;
        return;
      }
      setStartedGame(blueTeam);
      blueTeam.sendDataToClient("OTHERLEFTPREGM");
      server.sendDataToAll("ADDGAME#" + blueTeam.getUserName() + "#" + getFormattedGameAmount());
    } else if (persona == blueTeam) {
      blueTeam = null;
      blueTeamReady = redTeamReady = false;
      if (redTeam == null) {
        server.removeGame(this);
        running = false;
        return;
      }
      setStartedGame(redTeam);
      redTeam.sendDataToClient("OTHERLEFTPREGM");
      server.sendDataToAll("ADDGAME#" + redTeam.getUserName() + "#" + getFormattedGameAmount());
    }

  }

  private void setStartedGame(PICAIUServerPersona persona) {
    String oldName = getFormattedGameName();
    startedGame = persona;
    server.changeGameName(oldName, getFormattedGameName());
  }

  boolean hasOnePlayer() {
    return (redTeam != null && blueTeam == null) || (redTeam == null && blueTeam != null);
  }

  boolean hasNoPlayers() {
    return redTeam == null && blueTeam == null;
  }

  void setReady(PICAIUServerPersona persona) {
    if (redTeam == persona) {
      redTeamReady = true;
      if (blueTeam != null) {
        blueTeam.sendDataToClient("OTHERREADY");
      }
    } else if (blueTeam == persona) {
      blueTeamReady = true;
      if (redTeam != null) {
        redTeam.sendDataToClient("OTHERREADY");
      }
    }

    if (blueTeamReady && redTeamReady) {
      //initiate game
      int total = blueTeam.getNumberOfUnitsTotal() + redTeam.getNumberOfUnitsTotal();
      blueTeam.sendDataToClient("STRTUNTRSFR#" + total + "#" + blueTeam.getMoney() + "#" + blueTeam.getTeam());
      redTeam.sendDataToClient("STRTUNTRSFR#" + total + "#" + redTeam.getMoney() + "#" + redTeam.getTeam());
    }
  }

  void sendToOtherInfo(PICAIUServerPersona persona) {
    if (redTeam == persona) {
      if (blueTeam != null) {
        blueTeam.sendDataToClient("SETENEMYMONEY#" + persona.getUserName() + "#" + persona.getMoney());
      }
    } else if (blueTeam == persona) {
      if (redTeam != null) {
        redTeam.sendDataToClient("SETENEMYMONEY#" + persona.getUserName() + "#" + persona.getMoney());
      }
    }
  }

  String getTeam(PICAIUServerPersona persona) {
    if (redTeam == persona) {
      return "RED";
    } else if (blueTeam == persona) {
      return "BLUE";
    } else {
      return "null";
    }
  }

  int getMinGameAmount() {
    return minAmount;
  }

  void setFinishedPlacing(PICAIUServerPersona persona) {
    if (persona == redTeam) {
      redTeamPlaced = true;
    } else if (persona == blueTeam) {
      blueTeamPlaced = true;
    }

    if (redTeamPlaced && blueTeamPlaced) {
      for (PICAIURecruittedUnit u : redTeam.recruitted) {
        blueTeam.sendDataToClient("AFP#R#" + u.getID() + "#" + u.getX() + "#" + u.getY());
      }
      for (PICAIURecruittedUnit u : blueTeam.recruitted) {
        redTeam.sendDataToClient("AFP#B#" + u.getID() + "#" + u.getX() + "#" + u.getY());
      }
    }
  }

  void setFinishedRetrievingEnemyTroops(PICAIUServerPersona persona) {
    if (persona == redTeam) {
      redTeamOK = true;
    } else if (persona == blueTeam) {
      blueTeamOK = true;
    }

    if (redTeamOK && blueTeamOK) {
      //initiateEngine

      redUnits = redTeam.recruitted;
      blueUnits = blueTeam.recruitted;
      redTeam.init();
      blueTeam.init();
      Thread t = new Thread(this);
      t.start();
    }
  }

  /**
   * Begins the game engine and loops it
   */
  public void run() {
    try {
      init();
      long t1 = System.nanoTime();
      long t2;

      while (running) {
        //measure time
        t2 = System.nanoTime();
        long deltaT = ((t2 - t1) / 1000000);

        updateGame(deltaT);
        collisionDetection();
        ai();
        sendData();
        //let other threads run
        long sleepTime = (long) (16.6f - (deltaT));
        if (sleepTime < 0) {
          sleepTime = 0;
        }
        sleep(sleepTime);
        //yield();
        //measure time
        t1 = t2;
        //System.out.println(sleepTime);
      }
    } catch (Exception e) {
      System.out.println(e);
      e.printStackTrace();
    }

  }

  private void init() {
    //initiation code
  }

  private void updateGame(long deltaT) {
    for (PICAIURecruittedUnit u : redUnits) {
      u.update(deltaT);
    }
    for (PICAIURecruittedUnit u : blueUnits) {
      u.update(deltaT);
    }

    //remove all discovered locations
    ArrayList<PICAIUUndiscoveredLocation> toRemove = new ArrayList();
    for (PICAIUUndiscoveredLocation l : undiscoveredLocationsRed) {
      for (PICAIURecruittedUnit u : redUnits) {
        int a = (l.getX() - (int) u.getX()) * (l.getX() - (int) u.getX());
        int b = (l.getY() - (int) u.getY()) * (l.getY() - (int) u.getY());
        if (a + b < (u.getVision() / 2) * (u.getVision() / 2)) {
          toRemove.add(l);
        }
      }
    }
    for (PICAIUUndiscoveredLocation l : toRemove) {
      undiscoveredLocationsRed.remove(l);
    }
    toRemove.clear();
    for (PICAIUUndiscoveredLocation l : undiscoveredLocationsBlue) {
      for (PICAIURecruittedUnit u : blueUnits) {
        int a = (l.getX() - (int) u.getX()) * (l.getX() - (int) u.getX());
        int b = (l.getY() - (int) u.getY()) * (l.getY() - (int) u.getY());
        if (a + b < (u.getVision() / 2) * (u.getVision() / 2)) {
          toRemove.add(l);
        }
      }
    }
    for (PICAIUUndiscoveredLocation l : toRemove) {
      undiscoveredLocationsBlue.remove(l);
    }
  }

  private void collisionDetection() {
    //collision detection here
  }

  private void ai() {
    //artificial intelligence here
    aiCount++;
    if (aiCount >= aiAct) {
      aiCount = 0;
      for (PICAIURecruittedUnit u : redUnits) {
        u.updateAI();
      }
      for (PICAIURecruittedUnit u : blueUnits) {
        u.updateAI();
      }
    }
  }

  void addBounty(String team, String x, String y, String type, String value, String id) {
    PICAIURecruittedUnit unit;
    System.out.println(team);
    if (team.equals("R")) {
      unit = blueTeam.getUnit(id);
      System.out.println(unit);
      redBounties.add(new PICAIUBountyS(Integer.parseInt(x), Integer.parseInt(y), Integer.parseInt(type), Integer.parseInt(value), unit));
    } else {
      unit = redTeam.getUnit(id);
      System.out.println(unit);
      blueBounties.add(new PICAIUBountyS(Integer.parseInt(x), Integer.parseInt(y), Integer.parseInt(type), Integer.parseInt(value), unit));

    }
  }

  List getRedBounties() {
      return redBounties;
  }

  List getBlueBounties() {
      return blueBounties;
  }

  private synchronized void sendData() {
    for (PICAIURecruittedUnit u : redTeam.recruitted) {
      redTeam.sendCCDataToClient("U#" + u.getNetworkString());
      blueTeam.sendCCDataToClient("U#" + u.getNetworkString());
    }
    for (PICAIURecruittedUnit u : blueTeam.recruitted) {
      blueTeam.sendCCDataToClient("U#" + u.getNetworkString());
      redTeam.sendCCDataToClient("U#" + u.getNetworkString());
    }
  }

  boolean isRunning() {
    return running;
  }

  private void createUndiscoveredLocations() {
    for (int x = 100; x < mapWidth; x += 100) {
      for (int y = 100; y < mapHeight; y += 100) {
        boolean add = true;
        for (PICAIUVector v : blueBases) {
          int a = (x - (int) v.getX()) * (x - (int) v.getX());
          int b = (y - (int) v.getY()) * (y - (int) v.getY());
          if (a + b < (baseVision / 2) * (baseVision / 2)) {
            add = false;
            break;
          }
        }
        if (add) {
          undiscoveredLocationsBlue.add(new PICAIUUndiscoveredLocation(x, y));
        }

        add = true;
        for (PICAIUVector v : redBases) {
          int a = (x - (int) v.getX()) * (x - (int) v.getX());
          int b = (y - (int) v.getY()) * (y - (int) v.getY());
          if (a + b < (baseVision / 2) * (baseVision / 2)) {
            add = false;
            break;
          }
        }
        if (add) {
          undiscoveredLocationsRed.add(new PICAIUUndiscoveredLocation(x, y));
        }

      }
    }
  }

  /**
   * Loads into the memory the bases for each player
   */
  private void createBases() {
    blueBases.add(new PICAIUVector(180, mapHeight - 180));
    blueBases.add(new PICAIUVector(780, mapHeight - 180));
    blueBases.add(new PICAIUVector(1400, mapHeight - 180));
    blueBases.add(new PICAIUVector(180, mapHeight - 800));


    redBases.add(new PICAIUVector(mapWidth - 180, 180));
    redBases.add(new PICAIUVector(mapWidth - 780, 180));
    redBases.add(new PICAIUVector(mapWidth - 1400, 180));
    redBases.add(new PICAIUVector(mapWidth - 180, 800));

    bases.addAll(blueBases);
    bases.addAll(redBases);
  }

  public ArrayList<PICAIUUndiscoveredLocation> getUndiscoveredLocationsRed() {
    return undiscoveredLocationsRed;
  }

  public ArrayList<PICAIUUndiscoveredLocation> getUndiscoveredLocationsBlue() {
    return undiscoveredLocationsBlue;
  }

  public PICAIUUndiscoveredLocation getClosestUndiscoveredLocationRed(int x, int y) {
    PICAIUUndiscoveredLocation location = null;
    int dist = 1000000000;
    for (PICAIUUndiscoveredLocation l : undiscoveredLocationsRed) {
      int a = (l.getX() - x) * (l.getX() - x);
      int b = (l.getY() - y) * (l.getY() - y);
      if (a + b < dist) {
        dist = a + b;
        location = l;
      }
    }
    return location;
  }

  public PICAIUUndiscoveredLocation getClosestUndiscoveredLocationBlue(int x, int y) {
    PICAIUUndiscoveredLocation location = null;
    int dist = 1000000000;
    for (PICAIUUndiscoveredLocation l : undiscoveredLocationsBlue) {
      int a = (l.getX() - x) * (l.getX() - x);
      int b = (l.getY() - y) * (l.getY() - y);
      if (a + b < dist) {
        dist = a + b;
        location = l;
      }
    }
    return location;
  }
}
